#include <string.h>
#include "io_util.h"
#include "memory.h"
#include "rmff_queue.h"

#define READ_16_BE(x) dest->x = read_16_be(file)
#define READ_32_BE(x) dest->x = read_32_be(file)

#define SEEK_16_BE(x) x = seek_16_be(file)
#define SEEK_32_BE(x) x = seek_32_be(file)

#define SKIP_16(x) skip_bytes(file, 2)
#define SKIP_32(x) skip_bytes(file, 4)
#define SKIP(x) skip_bytes(file, x)

int rmff_type_specific_process(SceUID file, rmff_stream_t *stream, uint32_t len)
{
	uint32_t fourcc;
	uint16_t num_16;

	if(len <= 4)return -1;
	fourcc = read_32_be(file);
	if(fourcc == RA_FOURCC)
	{
		stream->type = STREAM_TYPE_AUDIO;
		num_16 = read_16_be(file);
		if(num_16 < 5 || num_16 > 5)
		{
			warning_log("Unknown ra version");
			return 0;
		}
		if(len < 0x46)return -1;
		sceIoLseek(file, 0x30, PSP_SEEK_CUR);
		num_16 = read_16_be(file);
//TODO: Add resample support
		if(num_16 !=  8000 && num_16 != 11050 && num_16 != 12000 && num_16 != 16000 && num_16 != 22050 &&
		   num_16 != 24000 && num_16 != 32000 && num_16 != 44100 && num_16 != 48000)
		{
			warning_log("Unsupport audio sample rate %d", num_16);
			return 0;
		}
		debug_log("sample rate: %d", num_16);
		stream->sample_rate = num_16;
		SKIP_16(file);
		stream->sample_size = read_16_be(file);
		if(stream->sample_size != 16)
		{
			warning_log("Unsupport audio sample size %d", stream->sample_size);
			return 0;
		}
		debug_log("sample size: %d", stream->sample_size);
		stream->channels = read_16_be(file);
		if(stream->channels == 0 || stream->channels > 2)
		{
			warning_log("Unsupport audio channels %d", stream->channels);
			return 0;
		}
		debug_log("channels: %d", stream->channels);
		SKIP_32(file);
		fourcc = read_32_be(file);
		switch(fourcc)
		{
			case AAC_FOURCC_1:
			case AAC_FOURCC_2:
				stream->codec = CODEC_AAC;
				debug_log("Found AAC");
				break;
			default:
				warning_log("Unsupport audio codec %08x", fourcc);
				return 0;
		}
		stream->present = 1;
	}
	else if(read_32_be(file) == RV_FOURCC)
	{
		stream->type = STREAM_TYPE_VIDEO;
		fourcc = read_32_be(file);
		switch(fourcc)
		{
			case RV10_FOURCC:
				warning_log("rv10 not supported yet");
				return 0;
			case RV20_FOURCC:
				warning_log("rv20 not supported yet");
				return 0;
			case RV30_FOURCC:
				warning_log("rv30 not supported yet");
				return 0;
			case RV40_FOURCC:
				stream->codec = CODEC_RV40;
				break;
			default:
				error_log("Unknown rv type");
				return 0;
		}
		stream->width = read_16_be(file);
		stream->height = read_16_be(file);
		SKIP_16(file);
		SKIP_32(file);
		stream->fps = read_16_be(file);
		stream->present = 1;
	}
	return 0;
}

inline void rmff_object_t_read(SceUID file, rmff_object_t *dest)
{
	READ_32_BE(id);
	READ_32_BE(size);
	READ_16_BE(version);
}

int rmff_rmf_t_read(SceUID file)
{
	rmff_object_t obj;

	rmff_object_t_read(file, &obj);
	if(obj.id != RM_HEADER_OBJECT || obj.version > 1)return -1;
/*
   There is a sample with chunk size = 0x10, in that case file version
   is a word. Note that the sample has chunk version = 0 like all other files.
*/
	if(obj.size == 0x10)SKIP_16(file_version);
	else if(obj.size == 0x12)SKIP_32(file_version);
	else return -1;
	SKIP_32(num_headers);
	return 0;
}

int rmff_prop_t_read(rmff_file_t *dest)
{
	rmff_object_t obj;
	SceUID file = dest->file_handle;

	rmff_object_t_read(file, &obj);
	if(obj.size != 0x32 || obj.version != 0)return -1;
	skip_bytes(file, 20);
	READ_32_BE(duration);
	SKIP_32(preroll);
	READ_32_BE(index_offset);
	READ_32_BE(data_offset);
	READ_16_BE(num_streams);
	if(dest->num_streams > max_streams)
	{
		error_log("max_streams exceed");
		return -1;
	}
	SKIP_16(flags);
	return 0;
}

#define READ_16_STR(x) read_16_str(file, &dest->x)
#define SKIP_16_STR(x) skip_16_str(file)

int rmff_cont_t_read(SceUID file, rmff_cont_t *dest)
{
	rmff_object_t obj;

	rmff_object_t_read(file, &obj);
	if(obj.size < 22 || obj.version != 0)return -1;
	SKIP_16_STR(title);
	SKIP_16_STR(author);
	SKIP_16_STR(copyright);
	SKIP_16_STR(comment);
	return 0;
}

#define READ_8_STR(x) read_8_str(file, &stream->x)

int rmff_mdpr_read(rmff_file_t *rmfile)
{
	SceUID file;
	uint16_t stream_id;
	uint32_t pos, len;
	rmff_stream_t *stream;
	rmff_object_t obj;

	file = rmfile->file_handle;
	rmff_object_t_read(file, &obj);
	if(obj.size < 46 || obj.version != 0)return -1;
	stream_id = read_16_be(file);
	stream = &rmfile->streams[stream_id];
	if(stream->present)fatal_log("Stream id conflict");
	stream->id = stream_id;
	SKIP_32(max_bit_rate);
//bit rate & start_time & duration is needed in AVStream
	stream->avg_bit_rate = read_32_be(file);
	SKIP_32(max_packet_size);
	SKIP_32(avg_packet_size);
	SKIP_32(start_time);
	SKIP_32(preroll);
	SKIP_32(duration);
	READ_8_STR(name);
	READ_8_STR(mime);
	len = read_32_be(file);
	pos = sceIoLseek(file, 0, PSP_SEEK_CUR) + len;
	if(rmff_type_specific_process(file, stream, len) < 0)return -1;
	sceIoLseek(file, pos, PSP_SEEK_SET);
	return 0;
}

int rmff_data_t_read(rmff_file_t *rmfile)
{
	SceUID file = rmfile->file_handle;
	rmff_object_t obj;

	if(rmfile->data_offset && sceIoLseek(file, 0, PSP_SEEK_CUR) != rmfile->data_offset)
		warning_log("Incorrect data chunk offset");
	rmff_object_t_read(file, &obj);
	if(obj.version != 0)return -1;
	SKIP_32(packets_left);
	rmfile->data_offset = read_32_be(file);
	return 0;
}

int rmff_open_file(char *filename, rmff_file_t *rmfile)
{
	SceUID file;

	if(!filename)return 0;
	file = sceIoOpen(filename, PSP_O_RDONLY, 0777);
	if(!file)return 0;
	memset(rmfile, 0, sizeof(rmff_file_t));
	rmfile->file_handle = file;
	return 1;
}

void rmff_close_file(rmff_file_t *rmfile)
{
	int i;

	if(!rmfile)return;
	sceIoClose(rmfile->file_handle);
	for(i = 0; i < rmfile->num_streams; i++)
		if(rmfile->streams[i].queue)rmff_queue_free(rmfile->streams[i].queue);
}

int rmff_read_headers(rmff_file_t *rmfile)
{
	uint32_t id;
	rmff_object_t obj;
	SceUID file = rmfile->file_handle;

	if(!rmfile || rmfile->fault)goto error;
	if(rmff_rmf_t_read(file) < 0)goto error;
	do
	{
		SEEK_32_BE(id);
		switch(id)
		{
			case RM_PROPERTIES_OBJECT:
				if(rmff_prop_t_read(rmfile) < 0)goto error;
				break;

			case RM_CONTENT_OBJECT:
				if(rmff_cont_t_read(file, &rmfile->cont_header) < 0)goto error;
				break;

			case RM_MEDIA_PROPERTIES_OBJECT:
				if(rmff_mdpr_read(rmfile) < 0)goto error;
				break;

			case RM_DATA_OBJECT:
				break;
			default:
				warning_log("Unknown object id: %08x", id);
				rmff_object_t_read(file, &obj);
				sceIoLseek(file, obj.size - 10, PSP_SEEK_CUR);
		}
	}while(id != RM_DATA_OBJECT);
	if(rmff_data_t_read(rmfile) < 0)goto error;
	return 0;

error:
	rmfile->fault = 1;
	return -1;
}

rmff_packet_t* rmff_read_packet(rmff_file_t *rmfile)
{
	SceUID file = rmfile->file_handle;
	rmff_packet_t *packet = 0;
	uint16_t object_version;
	int len, stream;

	if(!rmfile || rmfile->fault)goto error;
	object_version = read_16_be(file);
	if(object_version > 1)goto error;
	len = read_16_be(file) - 12 + object_version;
	if(len < 0)goto error;
	stream = read_16_be(file);
	if(stream >= rmfile->num_streams)goto error;
	packet = shin_alloc_packet();
	packet->length = len;
	packet->stream = &rmfile->streams[stream];
	packet->timestamp = read_32_be(file);
	sceIoLseek(file, 2 + object_version, PSP_SEEK_CUR);
	packet->data = shin_malloc(len);
	sceIoRead(file, packet->data, len);
	return packet;

error:
	rmfile->fault = 1;
	rmff_release_packet(packet);
	return 0;
}

void rmff_release_packet(rmff_packet_t *packet)
{
	if(!packet)return;
	shin_free(packet->data);
	shin_free_packet(packet);
}
